import axios from "axios";
import qs from "qs";

/** @name axios默认配置，如需export，必须改成函数形式，防止被外部修改 **/
const defaultAxiosConfig = {
  timeout: 120000,
  withCredentials: true,
  validateStatus(status) {
    return status >= 200 && status <= 500;
  },
};

const NEXT = Symbol("NEXT");
const isFun = (fn) => typeof fn === "function";

/** @typedef { import('axios').AxiosRequestConfig } AxiosRequestConfig */
/**
 * @name 自定义配置
 * @typedef CustomConfig
 * @property { 'url' | 'www-form' | 'form-data' | undefined  } content_type
 * @property { boolean } is_auth 当token存在时，是否自动携带上token
 * @property { boolean } auto_show_error_message 当token存在时，是否自动携带上token
 * @property { boolean } auto_download 是否自动下载
 * @property { string } down_type 下载资源类型：excel/...
 * @property { boolean } not_code 特殊接口可能没有code
 * @property { boolean } show_code_error 是否业务逻辑是否显示错误信息
 * @property { string } error_msg 配置错误提示，非code: -1（非业务逻辑，程序意外报错）时提示该消息
 */
export default class WeflyAxios {
  constructor(axiosConfig = {}) {
    this.instance = null;
    this.requestMiddlewares = [];
    this.responseMiddlewares = [];
    this.axiosConfig = {
      token_key: "authorization",
      repeat_cancel: true,
      error_default_handle: true,
      ...defaultAxiosConfig,
      ...axiosConfig,
    };
    this.init();
  }
  init() {
    this.instance = axios.create(this.axiosConfig);
    this.instance.interceptors.request.use(
      (config) => {
        return this.runMiddlewares(
          this.requestMiddlewares.filter((item) => item.fulfilled),
          "fulfilled",
          config,
        )();
      },
      (error) => {
        return this.runMiddlewares(
          this.requestMiddlewares.filter((item) => item.rejected),
          "rejected",
          error,
        )();
      },
    );
    this.instance.interceptors.response.use(
      (response) => {
        return this.runMiddlewares(
          this.responseMiddlewares.filter((item) => item.fulfilled),
          "fulfilled",
          response,
        )();
      },
      (error) => {
        return this.runMiddlewares(
          this.responseMiddlewares.filter((item) => item.rejected),
          "rejected",
          error,
        )();
      },
    );
  }

  /**
   * @name 运行中间件
   * @warn 只支持同步中间件，应该不会存在异步中间件的情况
   * @param middlewares
   * @param method
   * @param args
   * @return {*}
   */
  runMiddlewares(middlewares, method, args) {
    const next = () => this.next();
    return function () {
      function dispatch(i) {
        let fn = middlewares[i][method];
        // 如果无fn了，则表示中间件已经从第一个依次执行到最后一个中间件了
        if (!fn) return args;
        try {
          const result = fn(args, next);
          // 中间件的返回值，如果是undefined, 又不执行next()，则直接返回args
          if (result === undefined) {
            return args;
          }
          return result;
        } catch (err) {
          if (err !== NEXT) {
            throw err;
          } else {
            // 如果是NEXT，切换到下一个中间件
            if (i < middlewares.length - 1) {
              return dispatch.bind(null, i + 1)();
            }
          }
          // 如果能执行到这里，说明中间件已经执行完都没有返回值，直接返回args
          return args;
        }
      }
      return dispatch(0);
    };
  }
  next() {
    throw NEXT;
  }
  checkInterceptor(interceptor = {}) {
    const { fulfilled, rejected } = interceptor;
    if (fulfilled && !isFun(fulfilled)) {
      throw new Error("fulfilled must be a function");
    }
    if (rejected && !isFun(rejected)) {
      throw new Error("rejected must be a function");
    }
  }
  /**
   * @name 请求拦截配置
   * @param interceptor
   * @property { 'fulfilled' | 'rejected'  }
   * @property { function } fulfilled 当token存在时，是否自动携带上token
   * @property { function } rejected 当token存在时，是否自动携带上token
   */
  useRequest(interceptor = {}) {
    if (isFun(interceptor)) {
      interceptor = { fulfilled: interceptor };
    }
    const { fulfilled, rejected } = interceptor;
    this.checkInterceptor(interceptor);
    this.requestMiddlewares.push({
      fulfilled: fulfilled || null,
      rejected: rejected || null,
    });
    return this;
  }
  useResponse(interceptor = {}) {
    if (isFun(interceptor)) {
      interceptor = { fulfilled: interceptor };
    }
    const { fulfilled, rejected } = interceptor;
    this.checkInterceptor(interceptor);
    this.responseMiddlewares.push({
      fulfilled: fulfilled || null,
      rejected: rejected || null,
    });
    return this;
  }
  /**
   * 用于快捷处理Content-Type属性值
   * @param { string } method 请求方法
   * @param { object } params 请求参数
   * @param { string } type www-form：代表x-www-form-urlencoded编码； multi代表二进制文件上传；json：application/json
   * @returns {{params, "Content-Type": string}}
   */
  transformContentType(method, params, type) {
    switch (type) {
      case "url":
        return {
          params,
          "Content-Type": "application/json",
        };
      case "www-form":
        if (!["delete", "get"].includes(method)) {
          params = qs.stringify(params);
        }
        return {
          params,
          "Content-Type": "application/x-www-form-urlencoded",
        };
      case "form-data":
        return {
          params,
          "Content-Type": "multipart/form-data",
        };
      default:
        return {
          params,
          "Content-Type": "application/json",
        };
    }
  }
  /**
   * 核心请求方法，并统一处理请求参数
   * @param { AxiosRequestConfig  } axiosConfig axios配置
   * @param { CustomConfig } customConfig 自定义配置，必须以 多单词+下划线 的形式命名属性
   * @returns { Promise<T> }
   */
  request(axiosConfig, customConfig = {}) {
    let { method = "get", params, headers = {}, ...extendConfig } = axiosConfig;
    method = method.toLowerCase();
    const type = customConfig.content_type || "json"; // Content-Type
    // 处理content-type
    const transformData = this.transformContentType(method, params, type);
    const query = transformData.params;
    // axios配置+自定义配置
    const requestConfig = {
      ...extendConfig,
      method,
      headers: {
        "Content-Type": transformData["Content-Type"],
        ...headers,
      },
      customConfig: {
        auto_show_error_message: true,
        show_code_error: true,
        ...customConfig,
      },
    };
    if (["delete", "get"].includes(method) || type === "url") {
      requestConfig.params = query;
    } else {
      requestConfig.data = query;
    }
    return this.instance(requestConfig);
  }
}
